昨天我們已經快速建立TodoList的專案,並簡單的創造一個模版與切割好各個components。今天要來串接我們之前寫好的RESTfulAPI了。
[GET] /api/todos
[POST] /api/todos
[PUT] /api/todos/{id}
[DELETE] /api/todos/{id}
Components 元件圖
(1)首先,因為api網址為http://localhost:9100/
,TodoList 網址為http://localhost:3000/
互相溝通會有跨網域的問題,所以我們利用webpack提供的proxy將api的網址代理掉。
package.json
"proxy":"http://localhost:9100/api"
(2)準備todo list api 的CRUD吧,在src / service/ todos.js 與 src / service/ helper.js
src / service/ helper.js
實作一些API回應處理
export const handleResponse = (response) => {
return response.text().then((text) => {
const data = text && JSON.parse(text);
if (!response.ok) {
const errorLog = {
status: response.status,
code: (data && data.ErrorCode) || '',
msg: (data && data.ErrorMessage) || response.statusText,
};
return Promise.reject(errorLog);
}
return data;
});
};
撰寫CRUD的function
import { handleResponse } from './helper';
const getTodos =()=> {
const requestOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
};
return fetch(`/todos`, requestOptions)
.then(handleResponse)
.then((todos) => {
return todos;
});
};
const createTodos =(data)=> {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
};
return fetch(`/todos`, requestOptions)
.then(handleResponse)
.then((res) => {
return res;
});
};
const updateTodos =(id, data)=> {
const requestOptions = {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
};
return fetch(`/todos/${id}`, requestOptions)
.then(handleResponse)
.then((res) => {
return res;
});
};
const deleteTodos =(id)=> {
const requestOptions = {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
};
return fetch(`/todos/${id}`, requestOptions)
.then(handleResponse)
.then((res) => {
return res;
});
};
export const todosService = {
getTodos,
createTodos,
updateTodos,
deleteTodos
};
(3)ToDo這個最外層的父組件,包含了3個子組件,分別是TitleBox, TodoForm與TodoItems,所以會在ToDo這一個組件放置要操作的todolist資料,並藉由props將資料及方法傳遞給子組件接收。
import React from 'react';
import TitleBox from '../components/TitleBox';
import TodoForm from '../components/TodoForm';
import TodoItems from '../components/TodoItems';
import { todosService } from '../service//todo.js';
import { useState, useEffect } from 'react';
const ToDoList = () => {
const [todos, setTodos] = useState([]);
useEffect(() => {
// 頁面載入時,取得代辦事項列表
todosService.getTodos().then((data) => {
setTodos(data);
});
}, []);
const handleAdd = (text) => {
// 接收從TodoForm 呼叫的handleAdd()方法
const data = {
task: text,
};
todosService.createTodos(data).then((res) => {
data.id = res;
data.status = res;
todos.push(data);
setTodos([...todos]);
});
};
const handleUpdate = (id, data) => {
// 接收從TodoItems 呼叫的handleUpdate()方法
todosService.updateTodos(id, data).then((res) => {
const mapTodos = todos.map((todo) => {
if (todo.id === id) {
todo.status = data.status;
}
return todo;
});
setTodos(mapTodos);
});
};
const handleDelete = (id) => {
// 接收從TodoItems 呼叫的handleDelete()方法
todosService.deleteTodos(id).then((res) => {
todos.forEach((todo, index) => {
if (todo.id === id) {
todos.splice(index, 1);
}
});
setTodos([...todos]);
});
};
return (
<div className="container">
<TitleBox />
<div className="todo-box">
<TodoForm handleAdd={handleAdd} />
<TodoItems todos={todos} handleUpdate={handleUpdate} handleDelete={handleDelete} />
</div>
</div>
);
};
export default ToDoList;
(4)接著實作TodoItems的列表顯示,在頁面載入時要呼叫[GET]/todos API取得列表,todos
資料並透過props傳遞給子組件(TodoItems),子組件接收到,利用map
方法將資料渲染出來。
import React from 'react';
const TodoItems = ({ todos, handleUpdate, handleDelete }) => {
const done = 2;
const notDone = 1;
return (
<ul>
{todos.map((todo, index) => (
<li
className={todo.status === done ? 'checked' : ''}
key={index}
onClick={() => {
const data = {status: done};
if (todo.status === done) {
data.status = notDone;
}
handleUpdate(todo.id, data);
}}>
{todo.task} <span className="badge bg-red">生活</span>
<span className="close" onClick={(event)=> {
event.stopPropagation();
handleDelete(todo.id)
}}>X</span>
</li>
))}
</ul>
);
};
export default TodoItems;
(5)新增代辦事項,由父組件(ToDo)傳遞一個handleAdd
方法讓TodoFrom 點擊新增時,可以呼叫此方法並call [POST] /todos ,更改代辦事項資料集。
import React from 'react';
import { useState } from 'react';
const TodoForm = ({handleAdd}) => {
const [toDoText, setToDoText] = useState('');
const onInputChange = (event) => {
const value = event.target.value;
setToDoText(value);
}
return (
<div className="header">
<input type="text" id="todoInput" name="todoInput" value={toDoText} placeholder="New Item..." onChange={onInputChange}/>
<button type="submit" className="addBtn" onClick={()=> {handleAdd(toDoText)}}>
Add
</button>
</div>
)
}
export default TodoForm;
(5)更新代辦事項狀態,在TodoItems
接收從父組件傳來的handleUpdate()
方法,點擊項目執行此方法,觸發到父組件呼叫[PUT] /todos/{id},待成功後整理資料並渲染在畫面上。
const TodoItems = ({ todos, handleUpdate, handleDelete }) => {
const done = 2;
const notDone = 1;
return (
<ul>
{todos.map((todo, index) => (
<li
className={todo.status === done ? 'checked' : ''}
key={index}
onClick={() => {
const data = {status: done};
if (todo.status === done) {
data.status = notDone;
}
// 呼叫由props傳遞過來的handleUpdate()
handleUpdate(todo.id, data);
}}>
{todo.task} <span className="badge bg-red">生活</span>
</li>
))}
</ul>
);
};
(5)刪除代辦事項,在TodoItems
接收從父組件傳來的handleDelete()
方法,點擊「X」執行此方法,觸發到父組件呼叫[DELETE] /todos/{id},待成功後整理資料並渲染在畫面上。
<span
className="close"
onClick={(event) => {
event.stopPropagation();
handleDelete(todo.id);
}}>
X
</span>